
#include <BPainter>
#include "member_BGridLayout.h"

using namespace BWE;

member_BGridLayout::member_BGridLayout(BGridLayout* layout)
{
	boss = layout;
	spacing = 0;
	rowHandle = -1;
	columnHandle = -1;
	pressed = false;
	dirty = true;
}
member_BGridLayout::~member_BGridLayout()
{

}

void member_BGridLayout::expandTable(int row, int column)
{
	if (rowHeads.size() < row + 1)
	{
		rowHeads.resize(row + 1);
		items.setRow(row + 1);
	}
	if (colHeads.size() < column + 1)
	{
		colHeads.resize(column + 1);
		items.setColumn(column + 1);
	}
}
void member_BGridLayout::dirtyCell(int row, int column)
{
	LayoutItem& item = items[row][column];
	colHeads[column].dirty = true;
	rowHeads[row].dirty = true;
	item.dirty = true;
	dirty = true;
}

BRect member_BGridLayout::rowHandleRect(int index)
{
	int spacing = boss->spacing();
	GridHead& cell = rowHeads(index);
	return BRect(cell.rect.left(), cell.rect.bottom(), cell.rect.width(), spacing);
}
BRect member_BGridLayout::columnHandleRect(int index)
{
	int spacing = boss->spacing();
	GridHead& cell = colHeads(index);
	return BRect(cell.rect.right(), cell.rect.top(), spacing, cell.rect.height());
}
void member_BGridLayout::checkRowHandle(int& handle)
{
	if (handle < 0)
		return;
	{
		upLimit.reset();
		for (int r = handle; r >= 0; r--)
		{
			GridHead& cell = rowHeads[r];
			if(cell.sizePolicy & Policy_Expanding)
				upLimit.max() += cell.maxSize - cell.realSize;
			if(cell.sizePolicy & Policy_Shrinking)
				upLimit.min() -= cell.realSize - cell.minSize;
		}
	}
	{
		downLimit.reset();
		for (int r = handle + 1; r < rowHeads.size(); r++)
		{
			GridHead& cell = rowHeads[r];
			if (cell.sizePolicy & Policy_Expanding)
				downLimit.max() += cell.maxSize - cell.realSize;
			if (cell.sizePolicy & Policy_Shrinking)
				downLimit.min() -= cell.realSize - cell.minSize;
		}
	}
	if (upLimit.empty() || downLimit.empty())
		handle = -1;
}
void member_BGridLayout::checkColumnHandle(int& handle)
{
	if (handle < 0)
		return;
	{
		leftLimit.reset();
		for (int c = handle; c >= 0; c--)
		{
			GridHead& cell = colHeads[c];
			if (cell.sizePolicy & Policy_Expanding)
				leftLimit.max() += cell.maxSize - cell.realSize;
			if (cell.sizePolicy & Policy_Shrinking)
				leftLimit.min() -= cell.realSize - cell.minSize;
		}
	}
	{
		rightLimit.reset();
		for (int c = handle + 1; c < colHeads.size(); c++)
		{
			GridHead& cell = colHeads[c];
			if (cell.sizePolicy & Policy_Expanding)
				rightLimit.max() += cell.maxSize - cell.realSize;
			if (cell.sizePolicy & Policy_Shrinking)
				rightLimit.min() -= cell.realSize - cell.minSize;
		}
	}
	if (leftLimit.empty() || rightLimit.empty())
		handle = -1;
}
bool member_BGridLayout::dragHandle(const BPoint& pos)
{
	bool adjusted = false;
	BPoint change = pos - pressCenter;
	upLimit.limit(change.y());
	downLimit.limit(change.y());
	if (rowHandle >= 0 && change.y() != 0)
	{
		int upRemain = change.y();
		for (int r = rowHandle; r >= 0; r--)
		{
			GridHead& head = rowHeads[r];
			if (upRemain > 0 && !(head.sizePolicy & Policy_Expanding))
				continue;
			if (upRemain < 0 && !(head.sizePolicy & Policy_Shrinking))
				continue;
			head.dragSize = head.metaSize;
			if (head.dragSize + upRemain > head.maxSize)
			{
				upRemain -= head.maxSize - head.dragSize;
				head.dragSize = head.maxSize;
				continue;
			}
			if (head.dragSize + upRemain < head.minSize)
			{
				upRemain -= head.minSize - head.dragSize;
				head.dragSize = head.minSize;
				continue;
			}
			head.dragSize += upRemain;
			upRemain = 0;
			break;
		}
		int downRemain = -change.y();
		for (int r = rowHandle + 1; r < rowHeads.size(); r++)
		{
			GridHead& head = rowHeads[r];
			if (downRemain > 0 && !(head.sizePolicy & Policy_Expanding))
				continue;
			if (downRemain < 0 && !(head.sizePolicy & Policy_Shrinking))
				continue;
			head.dragSize = head.metaSize;
			if (head.dragSize + downRemain > head.maxSize)
			{
				downRemain -= head.maxSize - head.dragSize;
				head.dragSize = head.maxSize;
				continue;
			}
			if (head.dragSize + downRemain < head.minSize)
			{
				downRemain -= head.minSize - head.dragSize;
				head.dragSize = head.minSize;
				continue;
			}
			head.dragSize += downRemain;
			downRemain = 0;
			break;
		}
		if (upRemain == -downRemain)
		{
			adjusted = true;
			for (int r = 0; r < rowHeads.size(); r++)
			{
				GridHead& head = rowHeads[r];
				head.realSize = head.dragSize;
			}
			int totalHeight = boss->rect().height() - totalBaseSize.height();
			for (int r = 0; r < rowHeads.size(); r++)
			{
				GridHead& head = rowHeads[r];
				head.fixedStretch = -1.0f;
				if (totalHeight > 0 && head.realSize > head.hintSize)
				{
					head.fixedStretch = (float)(head.realSize - head.hintSize) / totalHeight * totalStretch.heightStretch();
					head.dirty = true;
				}
			}
		}
	}
	leftLimit.limit(change.x());
	rightLimit.limit(change.x());
	if (columnHandle >= 0 && change.x() != 0)
	{
		int leftRemain = change.x();
		for (int c = columnHandle; c >= 0; c--)
		{
			GridHead& head = colHeads[c];
			if (leftRemain > 0 && !(head.sizePolicy & Policy_Expanding))
				continue;
			if (leftRemain < 0 && !(head.sizePolicy & Policy_Shrinking))
				continue;
			head.dragSize = head.metaSize;
			if (head.dragSize + leftRemain > head.maxSize)
			{
				leftRemain -= head.maxSize - head.dragSize;
				head.dragSize = head.maxSize;
				continue;
			}
			if (head.dragSize + leftRemain < head.minSize)
			{
				leftRemain -= head.minSize - head.dragSize;
				head.dragSize = head.minSize;
				continue;
			}
			head.dragSize += leftRemain;
			leftRemain = 0;
			break;
		}
		int rightRemain = -change.x();
		for (int c = columnHandle + 1; c < colHeads.size(); c++)
		{
			GridHead& head = colHeads[c];
			if (rightRemain > 0 && !(head.sizePolicy & Policy_Expanding))
				continue;
			if (rightRemain < 0 && !(head.sizePolicy & Policy_Shrinking))
				continue;
			head.dragSize = head.metaSize;
			if (head.dragSize + rightRemain > head.maxSize)
			{
				rightRemain -= head.maxSize - head.dragSize;
				head.dragSize = head.maxSize;
				continue;
			}
			if (head.dragSize + rightRemain < head.minSize)
			{
				rightRemain -= head.minSize - head.dragSize;
				head.dragSize = head.minSize;
				continue;
			}
			head.dragSize += rightRemain;
			rightRemain = 0;
			break;
		}
		if (leftRemain == rightRemain)
		{
			adjusted = true;
			for (int c = 0; c < colHeads.size(); c++)
			{
				GridHead& head = colHeads[c];
				head.realSize = head.dragSize;
			}
			int totalWidth = boss->rect().width() - totalBaseSize.width();
			for (int c = 0; c < colHeads.size(); c++)
			{
				GridHead& head = colHeads[c];
				head.fixedStretch = -1.0f;
				if (totalWidth > 0 && head.realSize > head.hintSize)
				{
					head.fixedStretch = (float)(head.realSize - head.hintSize) / totalWidth * totalStretch.widthStretch();
					head.dirty = true;
				}
			}
		}
	}
	if (adjusted)
	{
		dirty = true;
		applyRects();
	}
	return adjusted;
}

void member_BGridLayout::prepareCells()
{
	if (!dirty)
		return;
	dirty = false;
	for (int r = 0; r < rowHeads.size(); r++)
	{
		GridHead& head = rowHeads[r];
		if (head.dirty)
		{
			head.dirty = false;
			head.finished = false;
			head.sizeStretch = head.fixedStretch;
			head.sizePolicy = head.fixedPolicy;
			head.hintSize = 0;
			head.minSize = 0;
			head.maxSize = 999999;
			head.fixedSize = 0;
			head.realSize = 0;

			for (int c = 0; c < colHeads.size(); c++)
			{
				LayoutItem& item = items[r][c];
				if (item.dirty)
					item.flush();
				if (item.ignored)
					continue;
				if (Policy heightPolicy = item.sizePolicy.heightPolicy())
				{
					if (head.fixedPolicy == Policy_None)
						head.sizePolicy |= item.sizePolicy.heightPolicy();
					if (heightPolicy == Policy_Fixed && head.hintSize < item.fixedSize.height())
						head.hintSize = item.fixedSize.height();
					if (heightPolicy & Policy_Preferred && head.hintSize < item.sizeHint.height())
						head.hintSize = item.sizeHint.height();
					if (head.minSize < item.minSize.height())
						head.minSize = item.minSize.height();
					if (head.maxSize < item.maxSize.height())
						head.maxSize = item.maxSize.height();
					if (head.fixedStretch < 0.0f && head.sizeStretch < item.stretch.heightStretch())
						head.sizeStretch = item.stretch.heightStretch();
				}
			}
		}
	}

	for (int c = 0; c < colHeads.size(); c++)
	{
		GridHead& head = colHeads[c];
		if (head.dirty)
		{
			head.dirty = false;
			head.finished = false;
			head.sizeStretch = head.fixedStretch;
			head.sizePolicy = head.fixedPolicy;
			head.hintSize = 0;
			head.minSize = 0;
			head.maxSize = 999999;
			head.fixedSize = 0;
			head.realSize = 0;

			for (int r = 0; r < rowHeads.size(); r++)
			{
				LayoutItem& item = items[r][c];
				if (item.dirty)
					item.flush();
				if (item.ignored)
					continue;
				if (item.sizePolicy == BSizePolicy())
					continue;
				if (Policy widthPolicy = item.sizePolicy.widthPolicy())
				{
					if (head.fixedPolicy == Policy_None)
						head.sizePolicy |= item.sizePolicy.widthPolicy();
					if (widthPolicy == Policy_Fixed && head.hintSize < item.fixedSize.width())
						head.hintSize = item.fixedSize.width();
					if (widthPolicy & Policy_Preferred && head.hintSize < item.sizeHint.width())
						head.hintSize = item.sizeHint.width();
					if (head.minSize < item.minSize.width())
						head.minSize = item.minSize.width();
					if (head.maxSize < item.maxSize.width())
						head.maxSize = item.maxSize.width();
					if (head.fixedStretch < 0.0f && head.sizeStretch < item.stretch.widthStretch())
						head.sizeStretch = item.stretch.widthStretch();
				}
			}
		}
	}
}
void member_BGridLayout::distributeWidth()
{
	const BMargin& margin = boss->margin();
	int spacing = boss->spacing();

	totalStretch.widthStretch() = 0.0f;
	int totalWidth = margin.width();
	int validCount = 0;
	for (int i = 0; i < colHeads.size(); i++)
	{
		GridHead& cell = colHeads[i];
		if (cell.prepare())
			validCount++;
		totalWidth += cell.hintSize + spacing;
		totalStretch.widthStretch() += cell.sizeStretch;
	}
	if (validCount == 0)
		return;
	totalWidth -= spacing;
	totalBaseSize.width() = totalWidth;
	int remainWidth = boss->rect().width() - totalBaseSize.width();
	while (remainWidth != 0)
	{
		float totalStretch = 0.0f;
		for (int c = 0; c < colHeads.size(); c++)
		{
			GridHead& cell = colHeads[c];
			if (cell.finished)
				continue;
			if (cell.sizeStretch == 0.0f)
			{
				cell.finished = true;
				continue;
			}
			if (remainWidth > 0 && !(cell.sizePolicy & Policy_Expanding))
			{
				cell.finished = true;
				continue;
			}
			if (remainWidth < 0 && !(cell.sizePolicy & Policy_Shrinking))
			{
				cell.finished = true;
				continue;
			}
			totalStretch += cell.sizeStretch;
		}
		if (totalStretch < 0.00001)
			break;

		int used = 0;
		for (int c = 0; c < items.column(); c++)
		{
			GridHead& cell = colHeads[c];
			if (cell.finished)
				continue;
			int width = (int)(cell.sizeStretch / totalStretch * remainWidth);
			if (width == 0)
				continue;
			if (width + cell.realSize > cell.maxSize)
			{
				used += cell.maxSize - colHeads[c].realSize;
				cell.realSize = cell.maxSize;
				cell.finished = true;
				continue;
			}
			if (width + cell.realSize < cell.minSize)
			{
				used += cell.minSize - colHeads[c].realSize;
				cell.realSize = cell.minSize;
				cell.finished = true;
				continue;
			}
			used += width;
			cell.realSize += width;
		}
		if (used == 0)
			break;
		remainWidth -= used;
	}
}
void member_BGridLayout::distributeHeight()
{
	const BMargin& margin = boss->margin();
	int spacing = boss->spacing();

	totalStretch.heightStretch() = 0.0f;
	int totalHeight = margin.height();
	int validCount = 0;
	for (int i = 0; i < rowHeads.size(); i++)
	{
		GridHead& cell = rowHeads[i];
		if (cell.prepare())
			validCount++;
		totalHeight += cell.hintSize + spacing;
		totalStretch.heightStretch() += cell.sizeStretch;
	}
	if (validCount == 0)
		return;
	totalHeight -= spacing;
	totalBaseSize.height() = totalHeight;
	int remainHeight = boss->rect().height() - totalBaseSize.height();
	while(remainHeight != 0)
	{
		float totalStretch = 0.0f;
		for (int r = 0; r < rowHeads.size(); r++)
		{
			GridHead& cell = rowHeads[r];
			if (cell.finished)
				continue;
			if (cell.sizeStretch == 0.0f)
			{
				cell.finished = true;
				continue;
			}
			if (remainHeight > 0 && !(cell.sizePolicy & Policy_Expanding))
			{
				cell.finished = true;
				continue;
			}
			if (remainHeight < 0 && !(cell.sizePolicy & Policy_Shrinking))
			{
				cell.finished = true;
				continue;
			}
			totalStretch += cell.sizeStretch;
		}
		if (totalStretch < 0.00001)
			break;

		int used = 0;
		for (int r = 0; r < items.row(); r++)
		{
			GridHead& cell = rowHeads[r];
			if (cell.finished)
				continue;
			int height = (int)(cell.sizeStretch / totalStretch * remainHeight);
			if (height == 0)
				continue;
			if (cell.realSize + height > cell.maxSize)
			{
				used += cell.maxSize - rowHeads[r].realSize;
				cell.realSize = cell.maxSize;
				cell.finished = true;
				continue;
			}
			if (cell.realSize + height < cell.minSize)
			{
				used += cell.minSize - cell.realSize;
				cell.realSize = cell.minSize;
				cell.finished = true;
				continue;
			}
			used += height;
			cell.realSize += height;
		}
		if(used == 0)
			break;
		remainHeight -= used;
	}
}
void member_BGridLayout::applyRects()
{
	const BRect& rect = boss->rect();
	const BMargin& margin = boss->margin();
	int spacing = boss->spacing();
	int posY = rect.y() + margin.top();
	for (int r = 0; r < rowHeads.size(); r++)
	{
		int height = rowHeads[r].realSize;
		int posX = rect.x() + margin.left();
		rowHeads[r].rect.set(posX, posY, rect.width() - margin.width(), height);
		for (int c = 0; c < colHeads.size(); c++)
		{
			int width = colHeads[c].realSize;
			LayoutItem& item = items[r][c];
			item.prepare(0, width, height);
			item.adjust(posX, posY, width, height);
			posX += spacing + width;
		}
		posY += spacing + height;
	}
	{
		int posY = rect.y() + margin.top();
		int posX = rect.x() + margin.left();
		for (int c = 0; c < colHeads.size(); c++)
		{
			int width = colHeads[c].realSize;
			colHeads[c].rect.set(posX, posY, width, rect.height() - margin.height());
			posX += spacing + width;
		}
	}

}
